/*
 * Copyright (C) 2016-2019, LomboTech Co.Ltd.
 * Authors:
 *	lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 */

#include <drm/drmP.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <drm/drm_of.h>
#include <drm/lombo_drm.h>
#include <drm/drm_mipi_dsi.h>
#include <linux/component.h>
#include <linux/of_reserved_mem.h>
#include <linux/of_platform.h>

#include "lombo_crtc.h"
#include "lombo_drv.h"
#include "lombo_framebuffer.h"
#include "lombo_scale.h"
#include "lombo_subdrv.h"
#include "lombo_rot.h"

#define lombo_exit_if(cond)			\
	do {					\
		if (cond) {			\
			line = __LINE__;	\
			goto exit;		\
		}				\
	} while (0)

static const char *boot_disp_cmdline;

#if !defined(MODULE) && defined(CONFIG_DRM_FBDEV_EMULATION)
static int __init lombo_disp_setup(char *str)
{
	boot_disp_cmdline = str;
	DRM_INFO("boot_disp_cmdline=%s\n",
		boot_disp_cmdline ? boot_disp_cmdline : "null");
	return 1;
}
__setup("disp=", lombo_disp_setup);
#endif

int lombo_disp_boot_sync_id(struct lombo_drv *drv,
		const char *name, uint32_t id)
{
	struct lombo_boot_disp *bd = drv->boot_disp;
	uint32_t target_id = 0;

	if (drv->skip_boot)
		return 1;

	if (!bd)
		return 0;

	if (!strncmp(name, "dc", 2) || !strncmp(name, "bld", 3))
		target_id = bd->dc_id;
	else if (!strncmp(name, "tcon", 4))
		target_id = bd->tcon_id;

	if (target_id == id)
		return 1;
	else
		return 0;
}

static int lombo_do_parse_dp_cmdline(
	struct lombo_boot_disp *bd, const char *p)
{
	char *endp;
	int line;
	int len;
	int ret;

	endp = strstr(p, ":");
	lombo_exit_if(!endp);
	len = endp - p;
	if ((len == 3) && !strncmp(p, "LCD", 3)) {
		p = endp + 1;
		ret = lombo_strtou32_base16(p, &endp, &(bd->output_w));
		lombo_exit_if(ret || (*endp != 'x'));
		p = endp + 1;
		ret = lombo_strtou32_base16(p, &endp, &(bd->output_h));
		lombo_exit_if(ret);
	} else {
		lombo_exit_if(1);
	}

	lombo_exit_if(*endp != '@');
	p = endp + 1;
	ret = lombo_strtou32_base16(p, &endp, &(bd->tcon_id));
	lombo_exit_if(ret);

	DRM_DEBUG("w=%d, h=%d, tcon_id=%d\n",
		bd->output_w, bd->output_h, bd->tcon_id);

	return 0;
exit:
	DRM_INFO("%s:%d. p=%s, endp=%s\n", __func__, line,
		p ? p : "null", endp ? endp : "null");
	return -EINVAL;
}

static int lombo_do_parse_fb_cmdline(
	struct lombo_boot_disp *bd, const char *p)
{
	char *endp;
	int line, ret;
	struct drm_format_name_buf buf;

	if (!strncmp(p, "AR24", 4)) {
		bd->fmt = DRM_FORMAT_ARGB8888;
		endp = (char *)p + 4;
	} else if (!strncmp(p, "RG24", 4)) {
		bd->fmt = DRM_FORMAT_RGB888;
		endp = (char *)p + 4;
	} else {
		lombo_exit_if(1);
	}

	/* parse w/h */
	if (*endp == ':') {
		p = endp + 1;
		ret = lombo_strtou32_base16(p, &endp, &(bd->width));
		lombo_exit_if(ret || (*endp != 'x'));
		p = endp + 1;
		ret = lombo_strtou32_base16(p, &endp, &(bd->height));
		lombo_exit_if(ret);
	} else {
		bd->width = bd->output_w;
		bd->height = bd->output_h;
	}

	/* parse pitch */
	lombo_exit_if(*endp != '+');
	p = endp + 1;
	ret = lombo_strtou32_base16(p, &endp, &(bd->pitch));
	lombo_exit_if(ret);

	/* parse copy region */
	if (*endp == '(') {
		p = endp + 1;
		ret = lombo_strtou32_base16(p, &endp, &(bd->copy_x));
		lombo_exit_if(ret || (*endp != ','));
		p = endp + 1;
		ret = lombo_strtou32_base16(p, &endp, &(bd->copy_y));
		lombo_exit_if(ret || (*endp != ','));
		p = endp + 1;
		ret = lombo_strtou32_base16(p, &endp, &(bd->copy_w));
		lombo_exit_if(ret || (*endp != ','));
		p = endp + 1;
		ret = lombo_strtou32_base16(p, &endp, &(bd->copy_h));
		lombo_exit_if(ret || (*endp != ')'));
		endp++;
	} else {
		bd->copy_x = 0;
		bd->copy_y = 0;
		bd->copy_w = bd->width;
		bd->copy_h = bd->height;
	}

	/* parse scn */
	if (*endp == '[') {
		p = endp + 1;
		ret = lombo_strtou32_base16(p, &endp, &(bd->scn_x));
		lombo_exit_if(ret || (*endp != ','));
		p = endp + 1;
		ret = lombo_strtou32_base16(p, &endp, &(bd->scn_y));
		lombo_exit_if(ret || (*endp != ','));
		p = endp + 1;
		ret = lombo_strtou32_base16(p, &endp, &(bd->scn_w));
		lombo_exit_if(ret || (*endp != ','));
		p = endp + 1;
		ret = lombo_strtou32_base16(p, &endp, &(bd->scn_h));
		lombo_exit_if(ret || (*endp != ']'));
		endp++;
	} else {
		bd->scn_x = 0;
		bd->scn_y = 0;
		bd->scn_w = bd->output_w;
		bd->scn_h = bd->output_h;
	}

	/* parse phy addr */
	lombo_exit_if(*endp != '#');
	p = endp + 1;
	ret = lombo_strtou64_base16(p, &endp, &(bd->phy_addr));
	lombo_exit_if(ret);

	/* parse win id */
	lombo_exit_if(*endp != '@');
	p = endp + 1;
	ret = lombo_strtou32_base16(p, &endp, &(bd->win_id));
	lombo_exit_if(ret);

	/* parse dc id */
	lombo_exit_if(*endp != '@');
	p = endp + 1;
	ret = lombo_strtou32_base16(p, &endp, &(bd->dc_id));
	lombo_exit_if(ret);

	DRM_DEBUG("fmt(%s)\n", drm_get_format_name(bd->fmt, &buf));
	DRM_DEBUG("w=%d, h=%d, pitch=%d, addr=0x%llx\n",
		bd->width, bd->height, bd->pitch, bd->phy_addr);
	DRM_DEBUG("COPY(%d,%d,%d,%d), SCN[%d,%d,%d,%d]\n",
		bd->copy_x, bd->copy_y, bd->copy_w, bd->copy_h,
		bd->scn_x, bd->scn_y, bd->scn_w, bd->scn_h);
	DRM_DEBUG("win(%d),dc(%d)\n", bd->win_id, bd->dc_id);

	return 0;
exit:
	DRM_INFO("%s:%d. p=%s, endp=%s\n", __func__, line,
		p ? p : "null", endp ? endp : "null");
	return -EINVAL;
}

static int lombo_do_parse_boot_disp_cmdline(
	struct lombo_boot_disp *bd, const char *p)
{
	char *endp;
	int ret;

	endp = strstr(p, ";");
	if (!endp) {
		DRM_INFO("%s: line=%d\n", __func__, __LINE__);
		return -EINVAL;
	}

	ret = lombo_do_parse_dp_cmdline(bd, p);
	if (ret)
		return ret;

	p = endp + 1;
	ret = lombo_do_parse_fb_cmdline(bd, p);

	return ret;
}

static int lombo_parse_boot_disp_cmdline(struct lombo_drv *drv)
{
	const char *cmdline = boot_disp_cmdline;
	struct lombo_boot_disp *bd;
	int ret;

	if (!cmdline || (cmdline[0] == '\0'))
		return -EINVAL;

	bd = kzalloc(sizeof(*bd), GFP_KERNEL);
	if (!bd) {
		DRM_INFO("%s: alloc for boot_disp failed\n", __func__);
		return -ENOMEM;
	}

	ret = lombo_do_parse_boot_disp_cmdline(bd, cmdline);
	if (!ret)
		drv->boot_disp = bd;
	else
		kfree(bd);

	return ret;
}

static void lombo_free_boot_disp(struct lombo_drv *drv)
{
	struct lombo_boot_disp *bd = drv->boot_disp;

	if (bd) {
		drv->boot_disp = NULL;
		kfree(bd);
	}
}

static int lombo_drv_firstopen(struct drm_device *drm)
{
	DRM_DEBUG_KMS("\n");

	return 0;
}

static int lombo_drv_open(struct drm_device *drm, struct drm_file *file_priv)
{
	struct lombo_file_private *efile_priv;

	DRM_DEBUG_KMS("\n");

	efile_priv = kzalloc(sizeof(*efile_priv), GFP_KERNEL);
	if (!efile_priv)
		return -ENOMEM;

	file_priv->driver_priv = efile_priv;

	return lombo_subdrv_open(drm, file_priv);
}

static void lombo_drv_preclose(struct drm_device *drm,
	struct drm_file *file_priv)
{
	DRM_DEBUG_KMS("\n");
}

static void lombo_drv_postclose(struct drm_device *drm,
	struct drm_file *file_priv)
{
	DRM_DEBUG_KMS("driver_priv is %s NULL\n",
		      file_priv->driver_priv ? "not" : "");

	if (!file_priv->driver_priv)
		return;

	lombo_subdrv_close(drm, file_priv);

	kfree(file_priv->driver_priv);
	file_priv->driver_priv = NULL;
}

static void lombo_drv_lastclose(struct drm_device *file_priv)
{
	DRM_DEBUG_KMS("\n");
}

#ifdef CONFIG_DEBUG_FS

static struct drm_info_list lombo_drv_debugfs_list[] = {
	{ "lombo_kms_dump", lombo_crtc_kms_show, 0 },
};

static int lombo_drv_debugfs_init(struct drm_minor *minor)
{
	struct drm_device *dev = minor->dev;
	int ret;

	if (minor->type == DRM_MINOR_RENDER)
		return 0;

	ret = drm_debugfs_create_files(lombo_drv_debugfs_list,
			ARRAY_SIZE(lombo_drv_debugfs_list),
			minor->debugfs_root, minor);
	if (ret)
		dev_err(dev->dev, "install lombo_drv_debugfs_list failed\n");

	return ret;
}

#endif /* #ifdef CONFIG_DEBUG_FS */

DEFINE_DRM_GEM_CMA_FOPS(lombo_drv_fops);

static int lombo_ignore_ioctl_permit(
	struct drm_file *file_priv, unsigned int cmd)
{
	struct lombo_file_private *pfile = file_priv->driver_priv;

	switch (cmd) {
	case LOMBO_IGNORE_IOCTL_PERMIT_SET:
		pfile->ignore_ioctl_permit = 1;
		break;
	case LOMBO_IGNORE_IOCTL_PERMIT_UNSET:
		pfile->ignore_ioctl_permit = 0;
		break;
	case LOMBO_IGNORE_IOCTL_PERMIT_GET:
		return pfile->ignore_ioctl_permit;
	default:
		return -EINVAL;
	}

	return 0;
}

int lombo_ignore_ioctl_permit_get(struct drm_file *file_priv)
{
	return lombo_ignore_ioctl_permit(file_priv,
					 LOMBO_IGNORE_IOCTL_PERMIT_GET);
}
EXPORT_SYMBOL(lombo_ignore_ioctl_permit_get);

static int lombo_ignore_ioctl_permit_ioctl(struct drm_device *dev,
	void *data, struct drm_file *file_priv)
{
	unsigned int *cmd = data;
	int ret;

	ret = lombo_ignore_ioctl_permit(file_priv, *cmd);
	if (*cmd == LOMBO_IGNORE_IOCTL_PERMIT_GET) {
		*cmd = ret;
		ret = 0;
	}

	return ret;
}

const static struct drm_ioctl_desc lombo_drv_ioctls[] = {
	/* lombo wb ioctls */
	DRM_IOCTL_DEF_DRV(LOMBO_WB_SET_PARA,
			lombo_wb_set_para_ioctl, DRM_MASTER),
	DRM_IOCTL_DEF_DRV(LOMBO_WB_QUEUE_BUF,
			lombo_wb_queue_buf_ioctl, DRM_MASTER),
	DRM_IOCTL_DEF_DRV(LOMBO_WB_DEQUEUE_BUF,
			lombo_wb_dequeue_buf_ioctl, DRM_MASTER),
	DRM_IOCTL_DEF_DRV(LOMBO_WB_START,
			lombo_wb_start_ioctl, DRM_MASTER),
	DRM_IOCTL_DEF_DRV(LOMBO_WB_STOP,
			lombo_wb_stop_ioctl, DRM_MASTER),

	DRM_IOCTL_DEF_DRV(LOMBO_IGNORE_IOCTL_PERMIT,
			lombo_ignore_ioctl_permit_ioctl,
			DRM_UNLOCKED),

	DRM_IOCTL_DEF_DRV(LOMBO_SCALE_OPEN,
		lombo_scale_open_ioctl, DRM_AUTH | DRM_RENDER_ALLOW),
	DRM_IOCTL_DEF_DRV(LOMBO_SCALE_CLOSE,
		lombo_scale_close_ioctl, DRM_AUTH | DRM_RENDER_ALLOW),
	DRM_IOCTL_DEF_DRV(LOMBO_SCALE_SET_TIMEOUT,
		lombo_scale_set_timeout_ioctl, DRM_AUTH | DRM_RENDER_ALLOW),
	DRM_IOCTL_DEF_DRV(LOMBO_SCALE_EXEC,
		lombo_scale_exec_ioctl, DRM_AUTH | DRM_RENDER_ALLOW),
	DRM_IOCTL_DEF_DRV(LOMBO_ROT_GET_VER,
		lombo_rot_get_ver_ioctl, DRM_AUTH | DRM_RENDER_ALLOW),
	DRM_IOCTL_DEF_DRV(LOMBO_ROT_OPEN,
		lombo_rot_open_ioctl, DRM_AUTH | DRM_RENDER_ALLOW),
	DRM_IOCTL_DEF_DRV(LOMBO_ROT_CLOSE,
		lombo_rot_close_ioctl, DRM_AUTH | DRM_RENDER_ALLOW),
	DRM_IOCTL_DEF_DRV(LOMBO_ROT_SET_TIMEOUT,
		lombo_rot_set_timeout_ioctl, DRM_AUTH | DRM_RENDER_ALLOW),
	DRM_IOCTL_DEF_DRV(LOMBO_ROT_EXEC,
		lombo_rot_exec_ioctl, DRM_AUTH | DRM_RENDER_ALLOW),
};

static struct drm_driver lombo_drv_driver = {
	.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |
			   DRIVER_ATOMIC | DRIVER_RENDER,

	/* Generic Operations */
	.firstopen = lombo_drv_firstopen,
	.open = lombo_drv_open,
	.preclose = lombo_drv_preclose,
	.postclose = lombo_drv_postclose,
	.lastclose = lombo_drv_lastclose,
	.fops = &lombo_drv_fops,
	.name = "lombo",
	.desc = "Lombo Soc DRM",
	.date = "20191028",
	.major = 1,
	.minor = 0,

	/* Lombo ioctl Operations */
	.ioctls = lombo_drv_ioctls,
	.num_ioctls = ARRAY_SIZE(lombo_drv_ioctls),

	/* GEM Operations */
	.gem_vm_ops = &drm_gem_cma_vm_ops,
	.gem_free_object_unlocked = drm_gem_cma_free_object,
	.dumb_create = drm_gem_cma_dumb_create,

	/* PRIME Operations */
	.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
	.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
	.gem_prime_export = drm_gem_prime_export,
	.gem_prime_import = drm_gem_prime_import,
	.gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
	.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
	.gem_prime_vmap		= drm_gem_cma_prime_vmap,
	.gem_prime_vunmap	= drm_gem_cma_prime_vunmap,
	.gem_prime_mmap		= drm_gem_cma_prime_mmap,

	/* DEBUGFS */
	#ifdef CONFIG_DEBUG_FS
	.debugfs_init = lombo_drv_debugfs_init,
	#endif
};

static inline bool lombo_pipeline_available(struct device_node *np)
{
	return np && of_device_is_available(np) &&
		of_get_available_child_count(np);
}

static int lombo_parse_disp_pipeline(struct drm_device *drm)
{
	struct lombo_drv *drv = drm->dev_private;

	#ifdef CONFIG_DRM_FBDEV_EMULATION
	int i, cnt, crtc_cnt = 0, dsi_cnt = 0;
	struct device_node *np = drm->dev->of_node, *remote,
		*tcon_np[2] = { 0 }, *dsi_np[2] = { 0 };
	char *remote_name;

	cnt = of_property_count_u32_elems(np, "components");
	if (cnt <= 0) {
		DRM_INFO("no components\n");
		return -ENODEV;
	}

	for (i = 0; i < cnt; i++) {
		remote = of_parse_phandle(np, "components", i);
		if (!remote) {
			DRM_ERROR("parse phandle of component[%d] fail\n", i);
			return -EINVAL;
		}
		if (!of_device_is_available(remote))
			continue;
		remote_name = (char *)of_node_full_name(remote);
		if (!strncmp(remote_name, "tcon", 4)) {
			tcon_np[crtc_cnt++] = remote;
			continue;
		}
		if (!strncmp(remote_name, "mipi_dsi", 8)) {
			dsi_np[dsi_cnt++] = remote;
			continue;
		}
	}

	if (crtc_cnt != drm->mode_config.num_crtc) {
		DRM_ERROR("parse fbdev count failed\n");
		return -EINVAL;
	}
	drv->fbdev_cnt = crtc_cnt;
	drv->pipeline = devm_kcalloc(drm->dev, drv->fbdev_cnt,
				sizeof(*drv->pipeline), GFP_KERNEL);
	if (!drv->pipeline) {
		DRM_ERROR("alloc for pipeline info fail\n");
		return -ENOMEM;
	}

	for (i = 0; i < drv->fbdev_cnt; i++) {
		int j;
		struct platform_device *pdev = NULL;
		struct mipi_dsi_device *dsi = NULL;
		struct drm_connector *con, *con_tmp;
		struct device_node *child;
		void *drvdata;
		bool config = true, bridge = false;

		cnt = of_property_count_u32_elems(tcon_np[i], "output");
		if (cnt < 0) {
			config = false;
			cnt = dsi_cnt;
			remote = dsi_np[i];
			if (!lombo_pipeline_available(remote)) {
				DRM_DEBUG_KMS("no dsi device\n");
				remote = tcon_np[i];
				if (!lombo_pipeline_available(remote)) {
					DRM_DEBUG_KMS("no def dev\n");
					continue;
				}
				cnt = 1;
			} else {
				child = of_get_next_available_child(
					remote, NULL);
				if (of_drm_find_bridge(child))
					bridge = true;
			}
		}
		for (j = 0; j < cnt; j++) {
			if (config) {
				remote = of_parse_phandle(tcon_np[i],
					"output", j);
				if (!lombo_pipeline_available(remote)) {
					DRM_DEBUG_KMS("no cfg dev\n");
					return -EINVAL;
				}
			}

			if (bridge)
				#ifdef CONFIG_DRM_MIPI_DSI
				dsi = of_find_mipi_dsi_device_by_node(child);
				#else
				dsi = NULL;
				#endif
			else
				pdev = of_find_device_by_node(remote);
			if (!dsi && !pdev) {
				DRM_ERROR("find output device%d fail\n", j);
				return -EINVAL;
			}
			drvdata = dev_get_drvdata(bridge ?
					&dsi->dev : &pdev->dev);

			if (remote == tcon_np[i])
				con = drv->possible_cons[i];
			else
				con = (struct drm_connector *)drvdata;

			list_for_each_entry(con_tmp,
				&drm->mode_config.connector_list, head) {
				if (con == con_tmp) {
					drv->pipeline[i].con[j] = con;
					drv->pipeline[i].output_dev_cnt++;
					break;
				}
			}
		}
	}

	#else
	drv->fbdev_cnt = 0;
	#endif

	return 0;
}

static int lombo_drv_bind(struct device *dev)
{
	struct drm_device *drm;
	struct lombo_drv *drv;
	int ret, i;
	u32 value;

	DRM_DEV_DEBUG_DRIVER(dev, "%d\n", __LINE__);

	drm = drm_dev_alloc(&lombo_drv_driver, dev);
	if (IS_ERR(drm))
		return PTR_ERR(drm);

	drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
	if (!drv) {
		ret = -ENOMEM;
		goto free_drm;
	}

	dev_set_drvdata(dev, drm);
	drm->dev_private = drv;

	#ifdef CONFIG_OF_RESERVED_MEM
	ret = of_reserved_mem_device_init(dev);
	if (ret && ret != -ENODEV) {
		DRM_DEV_ERROR(drm->dev, "Couldn't claim our memory region\n");
		goto free_drm;
	}
	#endif

	for (i = 0; i < LOMBO_FBDEV_MAX; i++) {
		/* get win id for fbdev */
		if (!of_property_read_u32_index(dev->of_node,
						"fbdev_win_id",
						i, &value))
			drv->fb_win_id[i] = (uint8_t)value;
		/* parse fb bpp */
		if (!of_property_read_u32_index(dev->of_node,
						"fb_bpp", i, &value))
			drv->fb_bpp[i] = value;
	}

	if (!of_property_read_u32(dev->of_node,
			"deferred_restore_fbdev", &value))
		drv->deferred_restore = value;

	drm_mode_config_init(drm);

	lombo_parse_boot_disp_cmdline(drv);

	ret = component_bind_all(dev, drm);
	if (ret) {
		DRM_DEV_ERROR(dev, "Couldn't bind all components\n");
		goto cleanup_mode_config;
	}

	ret = lombo_parse_disp_pipeline(drm);
	if (ret)
		goto cleanup_mode_config;

	/* drm_vblank_init calls kcalloc, which can fail */
	ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
	if (ret)
		goto cleanup_mode_config;

	drm->irq_enabled = true;

	ret = lombo_framebuffer_init(drm);
	if (ret) {
		DRM_DEV_ERROR(dev, "lombo_framebuffer_init fail\n");
		goto cleanup_mode_config;
	}

	/* Enable connectors polling */
	drm_kms_helper_poll_init(drm);

	ret = drm_dev_register(drm, 0);
	if (ret)
		goto free_framebuffer;

	if (!drv->deferred_restore)
		for (i = 0; i < drv->fbdev_cnt; i++)
			lombo_framebuffer_restore_fbdev(drm, drv->fbdev[i]);

	lombo_free_boot_disp(drv);

	if (drv->has_scale) {
		DRM_DEV_DEBUG(dev, "has %d scale\n", drv->has_scale);
		lombo_scale_module_init();
	}
	lombo_subdrv_probe(drm);

	return 0;

free_framebuffer:
	lombo_framebuffer_free(drm);
cleanup_mode_config:
	drm_mode_config_cleanup(drm);
free_drm:
	drm_dev_put(drm);
	return ret;
}

static void lombo_drv_unbind(struct device *dev)
{
	DRM_DEV_DEBUG_DRIVER(dev, "%d\n", __LINE__);
}

static const struct component_master_ops lombo_drv_master_ops = {
	.bind	= lombo_drv_bind,
	.unbind	= lombo_drv_unbind,
};

static int compare_of(struct device *dev, void *data)
{
	DRM_DEV_DEBUG_DRIVER(dev, "Comparing of node %pOF with %pOF\n",
			     dev->of_node, data);

	return dev->of_node == data;
}

static int lombo_drv_add_components(struct device *dev,
	struct component_match **matchptr)
{
	struct device_node *np = dev->of_node;
	int i, count  = 0;

	for (i = 0;; i++) {
		struct device_node *node = of_parse_phandle(
					np, "components", i);

		if (!node)
			break;

		if (!of_device_is_available(node))
			continue;

		DRM_DEV_DEBUG_DRIVER(dev, "add component[%s]\n",
				     of_node_full_name(node));
		drm_of_component_match_add(dev, matchptr, compare_of, node);
		count++;
	}

	return count;
}

static int lombo_drv_probe(struct platform_device *pdev)
{
	struct component_match *match = NULL;
	int count = 0;

	count = lombo_drv_add_components(&pdev->dev, &match);
	DRM_DEV_DEBUG_DRIVER(&pdev->dev, "count=%d\n", count);
	if (!count)
		return 0;

	return component_master_add_with_match(&pdev->dev,
					       &lombo_drv_master_ops,
					       match);
}

static int lombo_drv_remove(struct platform_device *pdev)
{
	DRM_DEV_DEBUG_DRIVER(&pdev->dev, "%d\n", __LINE__);

	component_master_del(&pdev->dev, &lombo_drv_master_ops);
	return 0;
}

static void lombo_drv_shutdown(struct platform_device *pdev)
{
	struct drm_device *drm = platform_get_drvdata(pdev);

	if (drm)
		drm_atomic_helper_shutdown(drm);
}

#ifdef CONFIG_PM_SLEEP

static int lombo_drv_pm_suspend(struct device *dev)
{
	struct drm_device *drm = dev_get_drvdata(dev);

	return drm_mode_config_helper_suspend(drm);
}

static int lombo_drv_pm_resume(struct device *dev)
{
	struct drm_device *drm = dev_get_drvdata(dev);

	drm_mode_config_helper_resume(drm);

	return 0;
}

#endif

static const struct dev_pm_ops lombo_drv_pm = {
	SET_SYSTEM_SLEEP_PM_OPS(lombo_drv_pm_suspend, lombo_drv_pm_resume)
};

static const struct of_device_id lombo_drv_of_table[] = {
	{ .compatible = "lombo,lombo-display" },
	{ }
};
MODULE_DEVICE_TABLE(of, lombo_drv_of_table);

static struct platform_driver lombo_drv_platform_driver = {
	.probe		= lombo_drv_probe,
	.remove		= lombo_drv_remove,
	.shutdown	= lombo_drv_shutdown,
	.driver		= {
		.owner	= THIS_MODULE,
		.name	= "lombo-drm",
		.pm	= &lombo_drv_pm,
		.of_match_table	= lombo_drv_of_table,
	},
};
module_platform_driver(lombo_drv_platform_driver);

MODULE_AUTHOR("lomboswer <lomboswer@lombotech.com>");
MODULE_DESCRIPTION("Lombo SoC DRM/KMS Driver");
MODULE_LICENSE("GPL");
